На плоскости
заданы n точек своими декартовыми
координатами. Найти минимальный периметр многоугольника, содержащего все эти
точки. Гарантируется, что искомый многоугольник имеет ненулевую площадь.
Вход. Первая строка содержит количество точек n (3 ≤ n ≤ 1000) на плоскости. Далее следуют n строк, каждая из которых содержит пару координат xi, yi (-10000 ≤ xi,
yi ≤ 10000). Все
числа целые, все точки различны.
Выход. Вывести длину
периметра искомого многоугольника с одним знаком после запятой.
Пример
входа |
Пример
выхода |
5 1 0 0 1 -1 0 0 -1 0 0 |
5.7 |
геометрия
– выпуклая оболочка
Искомый многоугольник представляет собой выпуклую оболочку заданного
множества точек. Ее будем искать обходом Грэхема.
Самая левая
точка (0, 0) находится в центре. В ней будет начинаться и заканчиваться
построение выпуклой оболочки. На рисунке справа приведена сортировка точек по
полярному углу. Точки (0, 0) и (5, 0) обязательно принадлежат выпуклой
оболочке.
Объявим класс
точка и операторы их сложения и вычитания.
class Point
{
public:
int x, y;
Point(int x =
0, int y = 0)
{
this->x
= x; this->y = y;
}
double len2()
const {return
x*x + y*y;}
};
Point operator+ (Point a, Point b)
{
return
Point(a.x+b.x,a.y+b.y);
}
Point operator- (Point a, Point b)
{
return
Point(a.x-b.x,a.y-b.y);
}
Функция PolarAngle возвращает полярный угол точки p в промежутке [-PI; PI].
double PolarAngle(Point p)
{
return
atan2(1.0*p.y,1.0*p.x);
}
Функция сортировки точек по полярному
углу. Если две точки a и b имеют одинаковый полярный угол, то
сортируем их по увеличению расстояния от начала координат.
int f(Point a, Point b)
{
if
(fabs(PolarAngle(a) - PolarAngle(b)) < EPS)
return
a.len2() < b.len2();
return
PolarAngle(a) < PolarAngle(b);
}
Функция TurnLeft возвращает истину, если точки a, b и c образуют левый поворот.
int TurnLeft(Point a, Point b, Point c)
{
Point q = b - a, w = c - b;
return q.x *
w.y - w.x * q.y > EPS;
}
Основная часть программы. Читаем в вектор пар v координаты
входных n точек.
scanf("%d",&n);
for(i = 0; i < n; i++)
{
scanf("%d
%d",&a,&b);
v.push_back(Point(a,b));
}
Ищем самую левую точку (точку с
наименьшей x координатой). Если таких
точек несколько, то берем нижнюю точку (точку с наименьшей y координатой). Занесем эту точку в v[0]. Она однозначно будет
лежать на выпуклой оболочке.
for(i = 1; i < n; i++)
{
if (v[i].x
< v[0].x) swap(v[i],v[0]);
if ((v[i].x
== v[0].x) && (v[i].y < v[0].y)) swap(v[i],v[0]);
}
Совершим параллельный перенос осей
координат так, чтобы точка v[0] стала центром. Отнимаем изо всех точек точку Center = v[0].
Center = v[0];
for(i = 0; i < n; i++) v[i] = v[i] -
Center;
Сортируем все точки, кроме начальной,
по полярному углу. Добавим в конец первую (центральную) точку.
sort(v.begin()+1,v.end(),f); v.push_back(v[0]); n++;
Запускаем обход Грэхема. Все точки от
v[0] до v[cur] образуют выпуклую
оболочку. Рассмотрим очередную точку v[i].
Если точки v[cur – 1], v[cur] и v[i] не образуют левую тройку, то v[cur] не будет принадлежать выпуклой оболочке. Производим в цикле
выталкивание таких точек v[cur],
после чего заносим v[i] в выпуклую
оболочку.
for(cur = 1, i = 2; i < n; i++)
{
while( (cur
> 0) && !TurnLeft(v[cur-1],v[cur],v[i])) cur--;
v[++cur] = v[i];
}
Возвращаем прежние координаты точкам.
for(i = 0; i <= cur; i++) v[i] = v[i]
+ Center;
Вычисляем периметр многоугольника, являющегося
выпуклой оболочкой.
for(i = 0; i < cur; i++)
p += sqrt(1.0*(v[i+1] - v[i]).len2());
Выводим значение периметра p.
printf("%.1lf\n",p);
#include <cstdio>
#include <vector>
#include <algorithm>
#include <cmath>
using namespace std;
class Point
{
public:
int x, y;
Point(int x = 0, int y = 0)
{
this->x = x; this->y
= y;
}
double len2() const {return x*x + y*y;}
};
Point operator+ (Point a,
Point b)
{
return Point(a.x+b.x,a.y+b.y);
}
Point operator- (Point a,
Point b)
{
return Point(a.x-b.x,a.y-b.y);
}
vector<Point> v, hull;
int i, n, cur, a, b;
double p;
int TurnLeft(Point a, Point b, Point c)
{
Point p = b - a, q = c - b;
return p.x *
q.y > q.x * p.y;
}
Сортируем точки по возрастанию
полярного угла относительно центральной v[0]. Точка b имеет больший полярный угол чем а (то есть a < b), если поворот (v[0], a, b)
является левым. Если все три точки v[0], a,
b коллинеарны, то a < b если только расстояние от начала координат до a меньше чем до b. В случае правого поворота (v[0], a, b) компаратор f должен
вернуть 0.
int f(Point a, Point b)
{
Point p = a - v[0], q = b - a;
if(p.x * q.y
== q.x * p.y)
return
a.len2() < b.len2();
return p.x *
q.y > q.x * p.y;
}
Еще один вариант реализации функции
сортировки. Вычтем из точек a и b точку v[0], получив векторы a
и b. Совместим начало вектора b с концом a.
Если векторы a и b образуют левый поворот, то полярный
угол исходной точки a меньше
полярного угла исходной точки b (a < b).
Если векторы a и b коллинеарны, то ближе к v[0] будет конец более короткого
вектора.
int f(Point a, Point b)
{
a = a - v[0]; b = b - v[0];
if(a.x * b.y
== b.x * a.y)
return
a.len2() < b.len2();
return a.x *
b.y > b.x * a.y;
}
int main(void)
{
scanf("%d",&n);
for(i = 0; i < n; i++)
{
scanf("%d %d",&a,&b);
v.push_back(Point(a,b));
}
for(i = 1; i < n; i++)
{
if (v[i].x < v[0].x) swap(v[i],v[0]);
if ((v[i].x == v[0].x) && (v[i].y <
v[0].y)) swap(v[i],v[0]);
}
sort(v.begin()+1,v.end(),f);
v.push_back(v[0]); n++;
v[0]
содержит самую левую нижнюю точку. Параллельный перенос координат (так чтобы
v[0] стало центром координат) можно не делать.
for(cur = 1, i = 2; i < n; i++)
{
while( (cur > 0) &&
!TurnLeft(v[cur-1],v[cur],v[i])) cur--;
v[++cur] = v[i];
}
for(i = 0; i < cur; i++)
p +=
sqrt(1.0*(v[i+1] - v[i]).len2());
printf("%.1lf\n",p);
return 0;
}